use std::ops::Deref;


use serde::{Serialize, Deserialize};


#[derive(Debug,Default,Clone, Copy , PartialEq,Serialize, Deserialize)]
pub struct Color(pub (f32,f32,f32,f32));



impl Color {
    pub fn from_rgba(r:f32,g:f32,b:f32,a:f32) -> Self {
        Color((r,g,b,a))
    }

    pub fn from_rgb(r:f32,g:f32,b:f32) -> Self {
        Color((r,g,b,1.0))
    }

    pub fn from_f32_arr(f32_set:[f32;3]) -> Self {
        Color((f32_set[0],f32_set[1],f32_set[2],1.0))
    }

    pub fn is_transparent(&self) -> bool {
        self.0.3 == 0.0
    }
    
}

impl Color {
    pub const WHITE:Color = Color((1.0,1.0,1.0,1.0));
    pub const BLACK:Color = Color((0.0,0.0,0.0,1.0));
    pub const RED:  Color = Color((1.0,0.0,0.0,1.0));
    pub const GREEN:Color = Color((0.0,1.0,0.0,1.0));
    pub const BLUE: Color = Color((0.0,0.0,1.0,1.0));
    pub const TRANSPARENT:Color = Color((0.0,0.0,0.0,0.0));
}
unsafe impl bytemuck::Pod for Color {}
unsafe impl bytemuck::Zeroable for Color {}


impl From<Rgba> for Color {
    fn from(c: Rgba) -> Self {
        Self((
            linear_f32_from_gamma_u8(c.0.0),
            linear_f32_from_gamma_u8(c.0.1),
            linear_f32_from_gamma_u8(c.0.2),
            linear_f32_from_linear_u8(c.0.3)
        ))
    }
}

impl Deref for Color {
    type Target = (f32,f32,f32,f32);

    fn deref(&self) -> &Self::Target {
        &self.0
    }

  
}


impl std::ops::Mul<f32> for Color {
    type Output = Self;
    fn mul(self, rhs: f32) -> Self {
        Color((self.0.0 * rhs, self.0.1 * rhs, self.0.2 * rhs, self.0.3 * rhs))
    }
}


#[allow(clippy::derive_hash_xor_eq)]
impl std::hash::Hash for Color {
    #[inline]
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        super::f32_hash(state, self.0.0);
        super::f32_hash(state, self.0.1);
        super::f32_hash(state, self.0.2);
        super::f32_hash(state, self.0.3);
    }
}


#[derive(Debug,Default,Clone, Copy , PartialEq,Serialize, Deserialize)]
pub struct Rgba(pub (u8,u8,u8,u8));


impl Rgba {
    pub fn new(r:u8,g:u8,b:u8,a:u8) -> Self {
        Self((r,g,b,a))
    }

    pub fn to_array(&self) -> [u8;4] {
        [self.0.0,self.0.1,self.0.2,self.0.3]
    }
}

impl Rgba {
    pub const TRANSPARENT:Rgba = Rgba((0,0,0,0));
    pub const WHITE:Rgba = Rgba((255,255,255,255));
    pub const RED:Rgba = Rgba((255,0,0,255));
    pub const MIDDLE_SPRING_GREEN:Rgba = Rgba(( 113 , 247 , 159 , 255 ));
    pub const MIDDLE_TURQUOISE:Rgba = Rgba(( 61 , 214 , 208 , 255 ));
    pub const KEPPEL:Rgba = Rgba(( 21 , 176 , 151 , 255 ));
    pub const CAFE_NOIR:Rgba = Rgba(( 81 , 60 , 44 , 255 ));
    pub const BISTRE:Rgba = Rgba(( 40 , 25 , 14 , 255 ));
    pub const BLACK:Rgba = Rgba(( 0 , 0 , 0 , 255 ));
    
}
unsafe impl bytemuck::Pod for Rgba {}
unsafe impl bytemuck::Zeroable for Rgba {}

impl From<Color> for Rgba {
    fn from(c: Color) -> Self {
        Self((
            gamma_u8_from_linear_f32(c.0.0),
            gamma_u8_from_linear_f32(c.0.1),
            gamma_u8_from_linear_f32(c.0.2),
            linear_u8_from_linear_f32(c.0.3)
        ))
    }
}

/// gamma [0, 255] -> linear [0, 1].
pub fn linear_f32_from_gamma_u8(s: u8) -> f32 {
    if s <= 10 {
        s as f32 / 3294.6
    } else {
        ((s as f32 + 14.025) / 269.025).powf(2.4)
    }
}

/// linear [0, 255] -> linear [0, 1].
/// Useful for alpha-channel.
#[inline(always)]
pub fn linear_f32_from_linear_u8(a: u8) -> f32 {
    a as f32 / 255.0
}

/// linear [0, 1] -> gamma [0, 255] (clamped).
/// Values outside this range will be clamped to the range.
pub fn gamma_u8_from_linear_f32(l: f32) -> u8 {
    if l <= 0.0 {
        0
    } else if l <= 0.0031308 {
        fast_round(3294.6 * l)
    } else if l <= 1.0 {
        fast_round(269.025 * l.powf(1.0 / 2.4) - 14.025)
    } else {
        255
    }
}

/// linear [0, 1] -> linear [0, 255] (clamped).
/// Useful for alpha-channel.
#[inline(always)]
pub fn linear_u8_from_linear_f32(a: f32) -> u8 {
    fast_round(a * 255.0)
}

fn fast_round(r: f32) -> u8 {
    (r + 0.5).floor() as _ // rust does a saturating cast since 1.45
}


/// gamma [0, 1] -> linear [0, 1] (not clamped).
/// Works for numbers outside this range (e.g. negative numbers).
pub fn linear_from_gamma(gamma: f32) -> f32 {
    if gamma < 0.0 {
        -linear_from_gamma(-gamma)
    } else if gamma <= 0.04045 {
        gamma / 12.92
    } else {
        ((gamma + 0.055) / 1.055).powf(2.4)
    }
}

/// linear [0, 1] -> gamma [0, 1] (not clamped).
/// Works for numbers outside this range (e.g. negative numbers).
pub fn gamma_from_linear(linear: f32) -> f32 {
    if linear < 0.0 {
        -gamma_from_linear(-linear)
    } else if linear <= 0.0031308 {
        12.92 * linear
    } else {
        1.055 * linear.powf(1.0 / 2.4) - 0.055
    }
}




// ----------------------------------------------------------------------------

/// Hue, saturation, value, alpha. All in the range [0, 1].
/// No premultiplied alpha.

#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Hsva {
    /// hue 0-1
    pub h: f32,
    /// saturation 0-1
    pub s: f32,
    /// value 0-1
    pub v: f32,
    /// alpha 0-1. A negative value signifies an additive color (and alpha is ignored).
    pub a: f32,
}

impl Hsva {
    pub fn new(h: f32, s: f32, v: f32, a: f32) -> Self {
        Self { h, s, v, a }
    }

    /// From `sRGBA` with premultiplied alpha
    pub fn from_srgba_premultiplied(srgba: [u8; 4]) -> Self {
        Self::from_rgba_premultiplied(
            linear_f32_from_gamma_u8(srgba[0]),
            linear_f32_from_gamma_u8(srgba[1]),
            linear_f32_from_gamma_u8(srgba[2]),
            linear_f32_from_linear_u8(srgba[3]),
        )
    }

    /// From `sRGBA` without premultiplied alpha
    pub fn from_srgba_unmultiplied(srgba: [u8; 4]) -> Self {
        Self::from_rgba_unmultiplied(
            linear_f32_from_gamma_u8(srgba[0]),
            linear_f32_from_gamma_u8(srgba[1]),
            linear_f32_from_gamma_u8(srgba[2]),
            linear_f32_from_linear_u8(srgba[3]),
        )
    }

    /// From linear RGBA with premultiplied alpha
    pub fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
        #![allow(clippy::many_single_char_names)]
        if a == 0.0 {
            if r == 0.0 && b == 0.0 && a == 0.0 {
                Hsva::default()
            } else {
                Hsva::from_additive_rgb([r, g, b])
            }
        } else {
            let (h, s, v) = hsv_from_rgb([r / a, g / a, b / a]);
            Hsva { h, s, v, a }
        }
    }

    /// From linear RGBA without premultiplied alpha
    pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
        #![allow(clippy::many_single_char_names)]
        let (h, s, v) = hsv_from_rgb([r, g, b]);
        Hsva { h, s, v, a }
    }

    pub fn from_additive_rgb(rgb: [f32; 3]) -> Self {
        let (h, s, v) = hsv_from_rgb(rgb);
        Hsva {
            h,
            s,
            v,
            a: -0.5, // anything negative is treated as additive
        }
    }

    pub fn from_rgb(rgb: [f32; 3]) -> Self {
        let (h, s, v) = hsv_from_rgb(rgb);
        Hsva { h, s, v, a: 1.0 }
    }

    pub fn from_srgb([r, g, b]: [u8; 3]) -> Self {
        Self::from_rgb([
            linear_f32_from_gamma_u8(r),
            linear_f32_from_gamma_u8(g),
            linear_f32_from_gamma_u8(b),
        ])
    }

    // ------------------------------------------------------------------------

    pub fn to_opaque(self) -> Self {
        Self { a: 1.0, ..self }
    }

    pub fn to_rgb(&self) -> [f32; 3] {
        rgb_from_hsv((self.h, self.s, self.v))
    }

    pub fn to_srgb(&self) -> [u8; 3] {
        let [r, g, b] = self.to_rgb();
        [
            gamma_u8_from_linear_f32(r),
            gamma_u8_from_linear_f32(g),
            gamma_u8_from_linear_f32(b),
        ]
    }

    pub fn to_rgba_premultiplied(&self) -> [f32; 4] {
        let [r, g, b, a] = self.to_rgba_unmultiplied();
        let additive = a < 0.0;
        if additive {
            [r, g, b, 0.0]
        } else {
            [a * r, a * g, a * b, a]
        }
    }

    /// Represents additive colors using a negative alpha.
    pub fn to_rgba_unmultiplied(&self) -> [f32; 4] {
        let Hsva { h, s, v, a } = *self;
        let [r, g, b] = rgb_from_hsv((h, s, v));
        [r, g, b, a]
    }

    pub fn to_srgba_premultiplied(&self) -> [u8; 4] {
        let [r, g, b, a] = self.to_rgba_premultiplied();
        [
            gamma_u8_from_linear_f32(r),
            gamma_u8_from_linear_f32(g),
            gamma_u8_from_linear_f32(b),
            linear_u8_from_linear_f32(a),
        ]
    }

    pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
        let [r, g, b, a] = self.to_rgba_unmultiplied();
        [
            gamma_u8_from_linear_f32(r),
            gamma_u8_from_linear_f32(g),
            gamma_u8_from_linear_f32(b),
            linear_u8_from_linear_f32(a.abs()),
        ]
    }
}

impl From<Hsva> for Color {
    fn from(hsva: Hsva) -> Color {
        let c = hsva.to_rgba_premultiplied();
        Color((c[0],c[1],c[2],c[3]))
    }
}
impl From<Color> for Hsva {
    fn from(rgba: Color) -> Hsva {
        Self::from_rgba_premultiplied(rgba.0.0, rgba.0.1 ,rgba.0.2,rgba.0.3)
    }
}

impl From<Hsva> for Rgba {
    fn from(hsva: Hsva) -> Rgba {
        Rgba::from(Color::from(hsva))
    }
}
impl From<Rgba> for Hsva {
    fn from(srgba: Rgba) -> Hsva {
        Hsva::from(Color::from(srgba))
    }
}

/// All ranges in 0-1, rgb is linear.
pub fn hsv_from_rgb([r, g, b]: [f32; 3]) -> (f32, f32, f32) {
    #![allow(clippy::many_single_char_names)]
    let min = r.min(g.min(b));
    let max = r.max(g.max(b)); // value

    let range = max - min;

    let h = if max == min {
        0.0 // hue is undefined
    } else if max == r {
        (g - b) / (6.0 * range)
    } else if max == g {
        (b - r) / (6.0 * range) + 1.0 / 3.0
    } else {
        // max == b
        (r - g) / (6.0 * range) + 2.0 / 3.0
    };
    let h = (h + 1.0).fract(); // wrap
    let s = if max == 0.0 { 0.0 } else { 1.0 - min / max };
    (h, s, max)
}

/// All ranges in 0-1, rgb is linear.
pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> [f32; 3] {
    #![allow(clippy::many_single_char_names)]
    let h = (h.fract() + 1.0).fract(); // wrap
    let s = s.clamp(0.0, 1.0);

    let f = h * 6.0 - (h * 6.0).floor();
    let p = v * (1.0 - s);
    let q = v * (1.0 - f * s);
    let t = v * (1.0 - (1.0 - f) * s);

    match (h * 6.0).floor() as i32 % 6 {
        0 => [v, t, p],
        1 => [q, v, p],
        2 => [p, v, t],
        3 => [p, q, v],
        4 => [t, p, v],
        5 => [v, p, q],
        _ => unreachable!(),
    }
}



// ----------------------------------------------------------------------------

/// Like Hsva but with the `v` value (brightness) being gamma corrected
/// so that it is somewhat perceptually even.

#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct HsvaGamma {
    /// hue 0-1
    pub h: f32,
    /// saturation 0-1
    pub s: f32,
    /// value 0-1, in gamma-space (~perceptually even)
    pub v: f32,
    /// alpha 0-1. A negative value signifies an additive color (and alpha is ignored).
    pub a: f32,
}

impl From<HsvaGamma> for Color {
    fn from(hsvag: HsvaGamma) -> Color {
        Hsva::from(hsvag).into()
    }
}

impl From<HsvaGamma> for Rgba {
    fn from(hsvag: HsvaGamma) -> Rgba {
        Color::from(hsvag).into()
    }
}

impl From<HsvaGamma> for Hsva {
    fn from(hsvag: HsvaGamma) -> Hsva {
        let HsvaGamma { h, s, v, a } = hsvag;
        Hsva {
            h,
            s,
            v: linear_from_gamma(v),
            a,
        }
    }
}

impl From<Rgba> for HsvaGamma {
    fn from(rgba: Rgba) -> HsvaGamma {
        Hsva::from(rgba).into()
    }
}

impl From<Color> for HsvaGamma {
    fn from(srgba: Color) -> HsvaGamma {
        Hsva::from(srgba).into()
    }
}

impl From<Hsva> for HsvaGamma {
    fn from(hsva: Hsva) -> HsvaGamma {
        let Hsva { h, s, v, a } = hsva;
        HsvaGamma {
            h,
            s,
            v: gamma_from_linear(v),
            a,
        }
    }
}

// ----------------------